package cn.virens.common.spring.annotations;

import cn.virens.common.util.exception.APIException;
import org.dromara.hutool.core.util.ObjUtil;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.MethodClassKey;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public abstract class VirAbstractAnnotationSource {
    private static final AnnotationAttributes NULL_ATTRIBUTE = new AnnotationAttributes();

    private final Map<MethodClassKey, AnnotationAttributes> attributeCache = new ConcurrentHashMap<>(1024);

    public final AnnotationAttributes findAnnotationAttributes(Method method, Class<?> targetClass) throws APIException {
        AnnotationAttributes attributes = attributeCache.computeIfAbsent(getCacheKey(method, targetClass), (k) -> {
            return ObjUtil.defaultIfNull(findAttributes0(method, targetClass), NULL_ATTRIBUTE);
        });

        // 如果注解属性为空就返回NULL
        if (attributes != NULL_ATTRIBUTE) {
            return attributes;
        } else {
            return null;
        }
    }

    protected abstract AnnotationAttributes findAttributes0(Method method, Class<?> targetClass);

    protected MethodClassKey getCacheKey(Method method, Class<?> targetClass) {
        return new MethodClassKey(method, targetClass);
    }

    protected AnnotationAttributes findAttributes1(Method method, Class<? extends Annotation> annotationType) {
        return AnnotatedElementUtils.findMergedAnnotationAttributes(method, annotationType, false, false);
    }

    protected AnnotationAttributes findAttributes1(Method method, Class<?> targetClass, Class<? extends Annotation> annotationType) {
        Method specificMethod = method != null ? AopUtils.getMostSpecificMethod(method, targetClass) : null;

        AnnotationAttributes attributes = findAttributes1(specificMethod, annotationType);
        if (attributes == null && specificMethod != method && annotationType != null) {
            return findAttributes1(method, annotationType);
        } else {
            return attributes;
        }
    }
}
